package top.someapp.applib.io;

import android.content.Context;
import android.os.Environment;
import androidx.annotation.NonNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import top.someapp.applib.Base;
import top.someapp.applib.CommonCharsets;
import top.someapp.applib.kits.Logs;
import top.someapp.applib.kits.Strings;

/**
 * 文件存储
 *
 * @author zwz
 * Created on 2019-10-15
 */
@SuppressWarnings("ResultOfMethodCallIgnored")
public class FileStorage implements Base.Initialization {

    private static final String TAG = "FileStorage";
    private static final int DEFAULT_IO_SIZE = 1024;
    private static FileStorage instance;
    private final String baseDir;
    private File publicPath;  // 外部公共存储目录，一般为：/sdcard/Documents/encoder/
    private File privatePath;  // 内部私有存储目录，一般为：/data/data/${appId}/files/
    private File tempBaseDir;      // 临时目录，压缩/解压缩时用, 一般为：/data/data/${appId}/cache/

    /// -------------------------------------------------------------------------------------------- 构造和初始化
    private FileStorage(String baseDir) {
        this.baseDir = baseDir;
    }

    public static FileStorage getInstance(String baseDir) {
        if (instance == null) {
            instance = new FileStorage(baseDir);
        }
        return instance;
    }

    private static void removeDir(File dir, boolean includeSelf) {
        for (File file : dir.listFiles()) {
            if (file.isFile()) {
                file.delete();
            }
            if (file.isDirectory()) {
                removeDir(file, true);
            }
        }
        if (includeSelf) {
            dir.delete();
        }
    }
    /// -------------------------------------------------------------------------------------------- ~构造和初始化

    @Override
    public boolean initialize(Context context) {
        Logs.i(TAG, "initialize FileStorage");
        if (publicPath == null) {
            publicPath = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_DOCUMENTS), baseDir);
        }

        boolean success = true;
        if (!publicPath.exists()) {
            success = publicPath.mkdirs();
        }

        if (privatePath == null) {
            privatePath = context.getFilesDir();
        }
        if (!privatePath.exists()) {
            success &= privatePath.mkdirs();
        }

        if (tempBaseDir == null) {
            tempBaseDir = context.getCacheDir();
        }
        if (!tempBaseDir.exists()) {
            success &= tempBaseDir.mkdirs();
        }

        return success;
    }

    public File getPublicPath() {
        return publicPath;
    }

    public File makeDirInPublicPath(String name) {
        File subDir = new File(publicPath, name);
        return subDir.exists() || subDir.mkdir() ? subDir : null;
    }

    public boolean hasFileInPublicPath(@NonNull String name) {
        if (publicPath == null || !publicPath.exists()) {
            return false;
        }
        return publicPath.isDirectory() && (new File(publicPath, name)).exists();
    }

    public File getPrivatePath() {
        return privatePath;
    }

    public File makeTempDir() {
        long now = System.currentTimeMillis() / 1000;  // ms => sec
        now = now % 86400;  // 1 day = 86400 sec
        int hour = (int) (now / (60 * 60)); // 1 hour = 60 * 60 sec
        now -= hour * 60 * 60;
        int minute = (int) (now / 60); // 1 minute = 60 sec
        now -= minute * 60;
        int second = (int) now; // remain seconds
        return makeTempDir(String.format(Locale.US, "temp-%02d%02d%02d", hour, minute, second));
    }

    public File makeTempDir(String name) {
        File dir = new File(tempBaseDir, name);
        // make sure make a clean dir
        if (dir.exists()) {
            dir.delete();
        }
        dir.mkdirs();
        return dir;
    }

    public void cleanTempDir() {
        if (tempBaseDir.exists()) {
            removeDir(tempBaseDir, false);
        }
    }

    public void cleanTempDir(File fileOrDir) {
        cleanTempDir(fileOrDir.getName());
    }

    public void cleanTempDir(String name) {
        if (tempBaseDir.exists()) {
            File file = new File(tempBaseDir, name);
            if (file.exists()) {
                if (file.isFile()) {
                    file.delete();
                } else if (file.isDirectory()) {
                    removeDir(file, true);
                }
            }
        }
    }

    public boolean writeToPublicPath(String fileName, InputStream ins) {
        File file = new File(publicPath, fileName);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            byte[] bytes = new byte[DEFAULT_IO_SIZE];
            for (int len; ; ) {
                len = ins.read(bytes);
                if (len < 1) {
                    break;
                }
                fos.write(bytes, 0, len);
            }
            fos.close();
            ins.close();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean writeToPublicPath(String fileName, byte[] bytes) {
        return writeToPublicPath(fileName, new ByteArrayInputStream(bytes));
    }

    public void copyToPublicPath(File src, String path, String name) throws IOException {
        if (Strings.isVisible(path)) {
            writeToPublicPath(path + name, new FileInputStream(src));
        } else {
            writeToPublicPath(name, new FileInputStream(src));
        }
    }

    public String readAsTextInPublicPath(String fileName) {
        File file = new File(publicPath, fileName);
        if (!file.exists()) {
            return null;
        }
        if (!file.isFile()) {
            return null;
        }

        ByteArrayOutputStream out = null;
        try {
            FileInputStream fis = new FileInputStream(file);
            out = new ByteArrayOutputStream(DEFAULT_IO_SIZE);
            byte[] bytes = new byte[DEFAULT_IO_SIZE];
            for (int len; ; ) {
                len = fis.read(bytes);
                if (len <= 0) {
                    break;
                }
                out.write(bytes, 0, len);
            }
            fis.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return out == null ? null : new String(out.toByteArray(), CommonCharsets.UTF_8);
    }
}